#include "StrBuf.h"

#include <string.h>
#include <unicode/ustring.h>
#include <fan-llvm/core/Core.h>
#include "ArgErr.h"
#include "IndexErr.h"
#include "Range.h"
#include "Str.h"
#include "StrBufOutStream.h"
#include "Type.h"


static Str* kNullStr;
static Str* kSpaceStr;

Type* StrBuf_classType = NULL;
VTable* StrBuf_classVTable = NULL;


/**********************************************
 *
 * Methods and fields
 *
 **********************************************/


/* Void make(Int capacity) */
void StrBuf_make_0(StrBuf* self)
{
  StrBuf_make(self, 16);
}


/* Void make(Int capacity) */
void StrBuf_make(StrBuf* self, Int capacity)
{
  Obj_make(VAL_MAKE_REF(self));
  self->buffer = NULL;
  self->capacity = -1;
  self->len = 0;
  StrBuf_setCapacity(self, capacity);
}


/* Str toStr() */
Str* StrBuf_toStr(Obj self)
{
  StrBuf* strBuf = VAL_GET_REF(self);
  return Str_newFromBuffer(strBuf->buffer, strBuf->len);
}


/* Int capacity */
Int StrBuf_capacity(StrBuf* self)
{
  return self->capacity;
}


/* Int capacity */
void StrBuf_setCapacity(StrBuf* self, Int val)
{
  if (val < 0)
    exceptionThrow(ArgErr_newWithFormattedMsg("Invalid capacity: %i", val));

  if (self->capacity < val)
  {
    Int minNewCapacity = 2 * self->capacity + 2;
    if (val < minNewCapacity)
      val = minNewCapacity;
    self->buffer = memoryRealloc(self->buffer, val * sizeof(Char));
    self->capacity = val;
  }
}


/* Bool isEmpty() */
Bool StrBuf_isEmpty(StrBuf* self)
{
  return (self->len == 0);
}


/* Int size() */
Int StrBuf_size(StrBuf* self)
{
  return self->len;
}


/* Int get(Int index) */
Int StrBuf_get(StrBuf* self, Int index)
{
  Int i = index;
  if (i < 0)
    i += self->len;
  if (i < 0 || i >= self->len)
    exceptionThrow(IndexErr_newWithIndex(index));
  return self->buffer[i];
}


/* This set(Int index, Int ch) */
StrBuf* StrBuf_set(StrBuf* self, Int index, Int ch)
{
  Int i = index;
  if (i < 0)
    i += self->len;
  if (i < 0 || i >= self->len)
    exceptionThrow(IndexErr_newWithIndex(index));
  self->buffer[i] = ch;
  return self;
}


/* This add(Obj? x) */
StrBuf* StrBuf_add(StrBuf* self, NullableObj x)
{
  Str* str = !VAL_IS_NULL(x) ? OBJ_VTABLE(x)->toStr(x) : kNullStr;
  StrBuf_setCapacity(self, self->len + str->len);
  u_memcpy(self->buffer + self->len, str->buffer, str->len);
  self->len += str->len;
  return self;
}


/* This addChar(Int ch) */
StrBuf* StrBuf_addChar(StrBuf* self, Int ch)
{
  StrBuf_setCapacity(self, self->len + 1);
  self->buffer[self->len] = ch;
  self->len++;
  return self;
}


/* This join(Obj? x, Str sep) */
StrBuf* StrBuf_join_1(StrBuf* self, NullableObj x)
{
  return StrBuf_join(self, x, kSpaceStr);
}


/* This join(Obj? x, Str sep) */
StrBuf* StrBuf_join(StrBuf* self, NullableObj x, Str* sep)
{
  if (self->len > 0)
    StrBuf_add(self, VAL_MAKE_REF(sep));
  StrBuf_add(self, x);
  return self;
}


/* This insert(Int index, Obj? x) */
StrBuf* StrBuf_insert(StrBuf* self, Int index, NullableObj x)
{
  Int i = index;
  if (i < 0)
    i += self->len;
  if (i < 0 || i > self->len)
    exceptionThrow(IndexErr_newWithIndex(index));

  Str* str = !VAL_IS_NULL(x) ? OBJ_VTABLE(x)->toStr(x) : kNullStr;
  StrBuf_setCapacity(self, self->len + str->len);
  u_memmove(self->buffer + i + str->len, self->buffer + i, self->len - i);
  u_memcpy(self->buffer + i, str->buffer, str->len);
  self->len += str->len;
  return self;
}


/* This remove(Int index) */
StrBuf* StrBuf_remove(StrBuf* self, Int index)
{
  Int i = index;
  if (i < 0)
    i += self->len;
  if (i < 0 || i >= self->len)
    exceptionThrow(IndexErr_newWithIndex(index));

  u_memmove(self->buffer + i, self->buffer + i + 1, self->len - 1);
  self->len--;
  return self;
}


/* This removeRange(Range r) */
StrBuf* StrBuf_removeRange(StrBuf* self, Range* r)
{
  Int start = Range_absStart(r, self->len);
  Int end = Range_absEnd(r, self->len);
  if (end < start)
    exceptionThrow(IndexErr_newWithRange(r));

  u_memmove(self->buffer + start, self->buffer + end + 1, self->len - end - 1);
  self->len -= start - end + 1;
  return self;
}


/* This clear() */
StrBuf* StrBuf_clear(StrBuf* self)
{
  self->len = 0;
  return self;
}


/* OutStream out() */
OutStream* StrBuf_out(StrBuf* self)
{
  return (OutStream*)StrBufOutStream_new(self);
}


/**********************************************
 *
 * Extra methods
 *
 **********************************************/


/* Create a new StrBuf instance and initialize it with StrBuf_make() */
StrBuf* StrBuf_new(Int capacity)
{
  StrBuf* strBuf = (StrBuf*)Obj_allocate(sizeof(StrBuf), StrBuf_classVTable);
  StrBuf_make(strBuf, capacity);
  return strBuf;
}


/* Add a sub-string of 'str' at the end of this buffer */
StrBuf* StrBuf_addSubstr(StrBuf* self, Str* str, Int start, Int end)
{
  if (start < 0 || end < start || end > str->len)
    exceptionThrow(IndexErr_newWithFormattedMsg("Range '%i..<%i' out of bounds", start, end));

  Int len = end - start;
  StrBuf_setCapacity(self, self->len + len);
  u_memcpy(self->buffer + self->len, str->buffer + start, len);
  self->len += len;
  return self;
}


/**********************************************
 *
 * Type-system functions
 *
 **********************************************/


Type* StrBuf_initType(void)
{
  if (StrBuf_classType == NULL)
  {
    Type* baseType = Obj_initType();
    VTable* vtable = StrBuf_initVTable();
    Type* type = Type_new("StrBuf", baseType, vtable, sizeof(StrBuf), sizeof(StrBufVTable));
    Type_registerCtorImplementation(type, "make", StrBuf_make_0, 0);
    Type_registerCtorImplementation(type, "make", StrBuf_make, 1);
    Type_registerMethodImplementation(type, "add", StrBuf_add, 1);
    Type_registerMethodImplementation(type, "addChar", StrBuf_addChar, 1);
    Type_registerMethodImplementation(type, "clear", StrBuf_clear, 0);
    Type_registerMethodImplementation(type, "get", StrBuf_get, 1);
    Type_registerMethodImplementation(type, "insert", StrBuf_insert, 2);
    Type_registerMethodImplementation(type, "isEmpty", StrBuf_isEmpty, 0);
    Type_registerMethodImplementation(type, "join", StrBuf_join_1, 1);
    Type_registerMethodImplementation(type, "join", StrBuf_join, 2);
    Type_registerMethodImplementation(type, "out", StrBuf_out, 0);
    Type_registerMethodImplementation(type, "remove", StrBuf_remove, 1);
    Type_registerMethodImplementation(type, "removeRange", StrBuf_removeRange, 1);
    Type_registerMethodImplementation(type, "set", StrBuf_set, 2);
    Type_registerMethodImplementation(type, "size", StrBuf_size, 0);
    Type_registerMethodImplementation(type, "toStr", StrBuf_toStr, 0);
    Type_registerFieldImplementation(type, "capacity", StrBuf_capacity, StrBuf_setCapacity);
    StrBuf_classType = type;
    StrBuf_initStatic();
  }
  return StrBuf_classType;
}


VTable* StrBuf_initVTable(void)
{
  if (StrBuf_classVTable == NULL)
  {
    VTable* vtable = memoryAlloc(sizeof(StrBufVTable));
    memcpy(vtable, Obj_initVTable(), sizeof(FanObjVTable));
    ((FanObjVTable*)vtable)->toStr = StrBuf_toStr;
    StrBuf_classVTable = vtable;
  }
  return StrBuf_classVTable;
}


void StrBuf_initStatic(void)
{
  kNullStr = Str_newFromCStr("null");
  kSpaceStr = Str_newFromCStr(" ");
}
